package com.atguigu.guli.service.ucenter.service.impl;

import com.atguigu.guli.common.util.utils.FormUtils;
import com.atguigu.guli.common.util.utils.HttpClientUtils;
import com.atguigu.guli.common.util.utils.MD5;
import com.atguigu.guli.service.base.consts.ServiceConsts;
import com.atguigu.guli.service.base.exception.GuliException;
import com.atguigu.guli.service.base.result.ResultCodeEnum;
import com.atguigu.guli.service.base.utils.JwtHelper;
import com.atguigu.guli.service.base.utils.JwtInfo;
import com.atguigu.guli.service.ucenter.config.WxLoginProperties;
import com.atguigu.guli.service.ucenter.entity.Member;
import com.atguigu.guli.service.ucenter.entity.vo.ApiMemberVo;
import com.atguigu.guli.service.ucenter.mapper.MemberMapper;
import com.atguigu.guli.service.ucenter.service.MemberService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpSession;
import java.net.URLEncoder;
import java.util.Map;
import java.util.UUID;

/**
 * <p>
 * 会员表 服务实现类
 * </p>
 *
 * @author yz
 * @since 2022-08-01
 */
@Service
@Slf4j
public class MemberServiceImpl extends ServiceImpl<MemberMapper, Member> implements MemberService {
    @Autowired
    RedisTemplate redisTemplate;

    /**
     * 用户注册业务实现
     *
     * @param memberVo
     */
    @Override
    public void register(ApiMemberVo memberVo) {
        //获取注册参数
        String code = memberVo.getCode();
        String nickname = memberVo.getNickname();
        String password = memberVo.getPassword();
        String mobile = memberVo.getMobile();

        //1.对参数进行非空判断
        if (StringUtils.isEmpty(code) ||//验证码为空
                StringUtils.isEmpty(nickname) ||//昵称为空
                StringUtils.isEmpty(password) || //密码为空
                !FormUtils.isMobile(mobile)) {//手机号码格式校验
            //如果验证码为空泽抛出异常
            throw new GuliException(ResultCodeEnum.PARAM_ERROR);
        }
        //2、验证验证码是否正确: 防止表单重复提交、保证手机号是注册用户自己的
        //获取redis中缓存的验证码
        Object redisCode = redisTemplate.opsForValue().get(ServiceConsts.SMS_CODE_PREFIX + mobile);
        if (redisCode == null || !code.equals(redisCode.toString())) {//如果验证码为空或者验证码与缓存中的不同
            throw new GuliException(ResultCodeEnum.CODE_ERROR);

        }
        //3、手机号码重复注册校验
        int count = this.count(new LambdaQueryWrapper<Member>().eq(Member::getMobile, mobile));
        if (count > 0) {//说明手机号重复被占用
            throw new GuliException(ResultCodeEnum.REGISTER_MOBLE_ERROR);
        }
        //4、注册
        Member member = new Member();
        member.setNickname(nickname);
        member.setAvatar("https://xaa0310-gui-file.oss-cn-shanghai.aliyuncs.com/avatar/2022/07/22/c8e37dcf998c478b8649becc90b457e4.jpg");
        member.setMobile(mobile);
        //密码加盐加密: 加盐的目的保证 暴力破解时得到的数据库加密密码的明文时，登录时仍然会经过盐处理 得到最终结果和数据库密文不一致
        //以后盐随机生成，和用户数据一起存到数据库表
        //以后登录时  先使用手机号查询数据(判断账号是否存在，如果存在查询密码和盐)
        // 使用登录密码和盐字符串 一起再加密 和数据库查询的密码进行比较  一致代表密码正确
        String encryptPwd = MD5.encrypt(ServiceConsts.MEMEBER_PWD_SALT + MD5.encrypt(password));//加盐后的密码
        member.setPassword(encryptPwd);

        this.save(member);
        redisTemplate.delete(ServiceConsts.SMS_CODE_PREFIX + mobile);//删除redis缓存中的短信验证码
    }

    @Override
    public String login(String mobile, String password) {
        //登录认证业务实现
        //1.根据手机号码查询用户数据
        Member member = this.getOne(new LambdaQueryWrapper<Member>().eq(Member::getMobile, mobile));
        //2.判断用户数据是否为空
        if (member == null) {
            throw new GuliException(ResultCodeEnum.LOGIN_MOBILE_ERROR);
        }
        //3.对账号密码进行检验
        //对明文密码进行加密和加盐处理
        String encryptPwd = MD5.encrypt(ServiceConsts.MEMEBER_PWD_SALT + MD5.encrypt(password));
        if (!member.getPassword().equals(encryptPwd)) {
            //如果密码校验失败
            throw new GuliException(ResultCodeEnum.LOGIN_PASSWORD_ERROR);
        }
        //对账号的状态进行判断
        if (member.getDisabled()) {
            //"是否禁用 1（true）已禁用，  0（false）未禁用"
            //如果账号状态禁用
            throw new GuliException(ResultCodeEnum.LOGIN_DISABLED_ERROR);
        }
        //4.创建Token
        JwtInfo jwtInfo = new JwtInfo();
        jwtInfo.setId(member.getId());//手机号码作为账号
        jwtInfo.setNickname(member.getNickname());
        jwtInfo.setAvatar(member.getAvatar());
        String token = JwtHelper.createToken(jwtInfo);
        return token;
    }

    @Autowired
    WxLoginProperties wxLoginProperties;

    @Override
    public String wxLogin(HttpSession session) {
        try {
            String url = "%s?" + "appid=%s" +//我们的应用在wx平台注册时分配的应用id   wxed9954c01bb89b47
                    "&redirect_uri=%s" +//用户授权登录后 的回调接口地址
                    "&response_type=code" + //访问该地址时希望得到code
                    "&scope=snsapi_login" + //访问时 授权的作用范围
                    "&state=%s" + // 防止恶意攻击的token字符串(防止表单重复提交)
                    "#wechat_redirect";
            String state = UUID.randomUUID().toString().replace("-", "");
            session.setAttribute("state", state);
            url = String.format(url, wxLoginProperties.getQrconnectUrl(), wxLoginProperties.getAppid(), URLEncoder.encode(wxLoginProperties.getRedirectUri(), "UTF-8"), state);
            return "redirect:" + url;
        } catch (Exception e) {
            //            e.printStackTrace();
            throw new GuliException(ResultCodeEnum.UNKNOWN_REASON, e);
        }
    }

    @Override
    public String wxCallback(String code, String state, HttpSession session) {

        try {
            //验证state
            Object sessionState = session.getAttribute("state");
            if (sessionState == null || !sessionState.equals(state)) {
                throw new GuliException(ResultCodeEnum.ILLEGAL_CALLBACK_REQUEST_ERROR);
            }
            //state使用后 销毁
            session.removeAttribute("state");
            //使用code获取wx用户的wx账号信息
            //1、根据code获取wx中的accessToken字符串(wx用户授权成功的token)
            String url = wxLoginProperties.getAccessTokenUrl() + "?" + "appid=" + wxLoginProperties.getAppid() + "&secret=" + wxLoginProperties.getAppsecret() + "&code=" + code + "&grant_type=authorization_code";
            HttpClientUtils client = new HttpClientUtils(url);
            client.get();
            String content = client.getContent();
            if (StringUtils.isEmpty(content) || content.contains("errcode")) {
                log.error(content);
                throw new GuliException(ResultCodeEnum.ILLEGAL_CALLBACK_REQUEST_ERROR);
            }
            Gson gson = new Gson();
            Map map = gson.fromJson(content, Map.class);
            log.info(map.toString());
            String accessToken = map.get("access_token").toString();
            /*
            openid代表唯一一个用户
             */
            String openid = map.get("openid").toString();
            //通过openid查询数据库中是否存在该用户
            Member member = this.getOne(new LambdaQueryWrapper<Member>().eq(Member::getOpenid, openid));


            if (member == null) {//如果用户不存在，则持久化到数据库
                url = wxLoginProperties.getUserInfoUrl() + "?" + "access_token=" + accessToken + "&openid=" + openid;
                client = new HttpClientUtils(url);
                client.get();
                content = client.getContent();
                if (StringUtils.isEmpty(content) || content.contains("errcode")) {
                    log.error(content);
                    throw new GuliException(ResultCodeEnum.ILLEGAL_CALLBACK_REQUEST_ERROR);
                }
                map = gson.fromJson(content, Map.class);
                String nickname = map.get("nickname").toString();
                String sex = map.get("sex").toString();
                String province = map.get("province").toString();
                String city = map.get("city").toString();
                String headimgurl = map.get("headimgurl").toString();
                String country = map.get("country").toString();
                log.info("用户数据：nickname={},sex={},province={},city={},country={},avatar={}", nickname, sex, province, city, country, headimgurl);
                //            return "hehe";

                //对解析出来的用户数据进行初始化
                member = new Member();
                member.setNickname(nickname);
                member.setAvatar(headimgurl);
                member.setOpenid(openid);
                this.save(member);
            } else if((System.currentTimeMillis()-member.getGmtModified().getTime())>=3*24*60*60*1000) {
                //对更新时间进行判断，如果超过三天则对用户进行更新
                //根据openid和accessToken重新解析获取用户数据
                url=wxLoginProperties.getUserInfoUrl()+"?"+ "access_token="+accessToken +
                        "&openid="+openid;
                client=new HttpClientUtils(url);
                client.get();
                content=client.getContent();
                if (StringUtils.isEmpty(content) || content.contains("errcode")) {
                    log.error(content);
                    throw new GuliException(ResultCodeEnum.ILLEGAL_CALLBACK_REQUEST_ERROR);
                }
                map=gson.fromJson(content, Map.class);
                //更新用户数据到数据库中
                member.setNickname(map.get("nickname").toString());
                member.setAvatar(map.get("headimgurl").toString());
                member.setGmtModified(null);//自动填充 如果属性值存在  不会自动填充
                member.setGmtCreate(null);
                this.updateById(member);

            }
            //if判断没进去则说明该用户第一次登录
            //将wx用户的数据创建为jwt token 交给3000前端项目的首页回显
            JwtInfo jwtInfo = new JwtInfo();
            jwtInfo.setAvatar(member.getAvatar());
            jwtInfo.setNickname(member.getNickname());
            jwtInfo.setId(member.getId());
            String token = JwtHelper.createToken(jwtInfo);
            //重定向 让浏览器访问3000项目的首页 并携带token参数
            // wx登录只能在本机测试
            return "redirect:http://localhost:3000?token=" + token;
        } catch (Exception e) {
            e.printStackTrace();
            throw new GuliException(ResultCodeEnum.ILLEGAL_CALLBACK_REQUEST_ERROR);
        }
    }
}
